package jxtend.processor.eclipse.visitor.handler.impl;

import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
import java.util.HashMap;
import java.util.Map;

import jxtend.annotation.keyword.rawType;
import jxtend.annotation.keyword.unused;
import jxtend.processor.eclipse.utils.EclipseProcessorUtils;
import jxtend.processor.eclipse.visitor.handler.ASTNodeHandlerVisitorContext;
import jxtend.processor.eclipse.visitor.handler.AbstractASTNodeVisitorHandler;

import org.eclipse.jdt.internal.compiler.ast.ASTNode;
import org.eclipse.jdt.internal.compiler.ast.Argument;
import org.eclipse.jdt.internal.compiler.ast.ConstructorDeclaration;
import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration;
import org.eclipse.jdt.internal.compiler.ast.LocalDeclaration;
import org.eclipse.jdt.internal.compiler.ast.MarkerAnnotation;
import org.eclipse.jdt.internal.compiler.ast.MethodDeclaration;
import org.eclipse.jdt.internal.compiler.ast.QualifiedTypeReference;
import org.eclipse.jdt.internal.compiler.ast.SingleMemberAnnotation;
import org.eclipse.jdt.internal.compiler.ast.SingleTypeReference;
import org.eclipse.jdt.internal.compiler.ast.StringLiteral;
import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration;
import org.eclipse.jdt.internal.compiler.impl.Constant;
import org.eclipse.jdt.internal.compiler.lookup.BlockScope;
import org.eclipse.jdt.internal.compiler.lookup.Scope;

@SuppressWarnings("restriction")
public class SuppressWarningsAliasAnnotationHandler extends AbstractASTNodeVisitorHandler {

	private static final Map<Class<? extends java.lang.annotation.Annotation>, String> suppressWarningsValueMap;
	static {
		suppressWarningsValueMap = new HashMap<Class<? extends java.lang.annotation.Annotation>, String>();
		suppressWarningsValueMap.put(unused.class, "unused");
		suppressWarningsValueMap.put(rawType.class, "rawtypes");
	}

	@Override
	public boolean endVisitBeforeResolve(ASTNodeHandlerVisitorContext context, ASTNode node, Scope scope) {		
		ASTNode parentNode = context.getParentNode();

		if (!(node instanceof MarkerAnnotation)) {
			return true;
		}

		String suppressWarningsValue = getSuppressWarningsAnnotationValue(parentNode, (MarkerAnnotation) node);
		if (suppressWarningsValue == null) {
			return true;
		}

		MarkerAnnotation aliasAnnotation = (MarkerAnnotation) node;

		SingleTypeReference overrideAnnotationTypeReference = new SingleTypeReference(
				"SuppressWarnings".toCharArray(),
				EclipseProcessorUtils.encodePos(aliasAnnotation.sourceStart, aliasAnnotation.sourceEnd));
		context.resolveExpression(overrideAnnotationTypeReference, (BlockScope) scope);

		SingleMemberAnnotation suppressWarningsAnnotation =
			new SingleMemberAnnotation(overrideAnnotationTypeReference, aliasAnnotation.sourceStart);
		suppressWarningsAnnotation.sourceEnd = aliasAnnotation.sourceEnd;
		suppressWarningsAnnotation.statementEnd = aliasAnnotation.statementEnd;
		suppressWarningsAnnotation.declarationSourceEnd = aliasAnnotation.declarationSourceEnd;
		

		StringLiteral uncheckedLiteral = new StringLiteral(
				suppressWarningsValue.toCharArray(), aliasAnnotation.sourceStart, aliasAnnotation.sourceEnd, 0);
		uncheckedLiteral.constant = Constant.NotAConstant;
		suppressWarningsAnnotation.memberValue = uncheckedLiteral;

		// trigger generation of the (single element of) memberValuePairs
		suppressWarningsAnnotation.memberValuePairs();

		context.replaceNode(suppressWarningsAnnotation);

		return true;
	}

	private static String getSuppressWarningsAnnotationValue(ASTNode parentNode, MarkerAnnotation aliasAnnotation) {
		if (!(aliasAnnotation.type instanceof QualifiedTypeReference)) {
			return null;
		}

		QualifiedTypeReference qualifiedTypeReference = (QualifiedTypeReference) aliasAnnotation.type;

		for (Map.Entry<Class<? extends java.lang.annotation.Annotation>, String> entry
				: suppressWarningsValueMap.entrySet()) {
			if (EclipseProcessorUtils.compoundNameEquals(qualifiedTypeReference.tokens, entry.getKey())
					&& isAllowToUseOnParent(parentNode, entry.getKey())) {

				return entry.getValue();
			}
		}

		return null;
	}

	private static boolean isAllowToUseOnParent(ASTNode parentNode, Class<? extends Annotation> annotation) {
		boolean result = false;

		Target target = annotation.getAnnotation(Target.class);

		ElementType[] elementTypes = target.value();
		for (ElementType elementType : elementTypes) {
			result |= (elementType == ElementType.TYPE && parentNode instanceof TypeDeclaration);
			result |= (elementType == ElementType.FIELD && parentNode instanceof FieldDeclaration);
			result |= (elementType == ElementType.METHOD && parentNode instanceof MethodDeclaration);
			result |= (elementType == ElementType.PARAMETER && parentNode instanceof Argument);
			result |= (elementType == ElementType.CONSTRUCTOR && parentNode instanceof ConstructorDeclaration);
			result |= (elementType == ElementType.LOCAL_VARIABLE
					&& parentNode instanceof LocalDeclaration && !(parentNode instanceof Argument));
			result |= (elementType == ElementType.ANNOTATION_TYPE && parentNode instanceof TypeDeclaration
					&& TypeDeclaration.kind(((TypeDeclaration) parentNode).modifiers) == TypeDeclaration.ENUM_DECL);

			if (result) {
				break;
			}
		}

		return result;
	}
}
